upgrade: Refuse chronologically older commits unless --allow-downgrade
authorColin Walters <walters@verbum.org>
Thu, 20 Feb 2014 23:25:56 +0000 (18:25 -0500)
committerColin Walters <walters@verbum.org>
Thu, 20 Feb 2014 23:25:56 +0000 (18:25 -0500)
We don't want to allow MITM attackers to intercept upgrade requests
and provide clients with older OS versions vulnerable to security
flaws.

Only "ostree admin upgrade" gets this behavior for now - whether we
want to do it for "ostree admin switch" is another question.

Makefile-tests.am
src/libostree/ostree-core.c
src/libostree/ostree-core.h
src/ostree/ot-admin-builtin-upgrade.c
tests/libtest.sh
tests/test-admin-upgrade-not-backwards.sh [new file with mode: 0644]

index d1a8803daabb8768083b2e59a6a956cf2a8c8506..13c5d60d57a2f51822417fe3025cfa016666f023 100644 (file)
@@ -37,6 +37,7 @@ testfiles = test-basic \
        test-admin-deploy-switch \
        test-admin-deploy-etcmerge-cornercases \
        test-admin-deploy-uboot \
+       test-admin-upgrade-not-backwards \
        test-setuid \
        test-delta \
        test-xattrs \
index 9b7a2379e4e9ede16c6d98425587bf2d28933baa..b8fa114a46f1ce8b9235b30ebd32d5ea20661ba7 100644 (file)
@@ -1755,3 +1755,11 @@ ostree_commit_get_parent (GVariant  *commit_variant)
     return NULL;
   return ostree_checksum_from_bytes_v (bytes);
 }
+
+guint64
+ostree_commit_get_timestamp (GVariant  *commit_variant)
+{
+  guint64 ret;
+  g_variant_get_child (commit_variant, 5, "t", &ret);
+  return GUINT64_FROM_BE (ret);
+}
index bd8e68fcf4fcc3b162f622c7c3547d6efc805809..de1698d27bdec1b29e777198197559d550feafda 100644 (file)
@@ -250,5 +250,6 @@ gboolean ostree_validate_structureof_dirmeta (GVariant      *dirmeta,
                                               GError       **error);
 
 gchar *  ostree_commit_get_parent            (GVariant  *commit_variant);
+guint64  ostree_commit_get_timestamp         (GVariant  *commit_variant);
 
 G_END_DECLS
index 1b86cddcc10038f3ebd58f986e487b84cf0a57ff..9fcae678eeb5655154828fc9c99dbbc0817ba03d 100644 (file)
 #include <glib/gi18n.h>
 
 static gboolean opt_reboot;
+static gboolean opt_allow_downgrade;
 static char *opt_osname;
 
 static GOptionEntry options[] = {
   { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Specify operating system root to use", NULL },
   { "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot, "Reboot after a successful upgrade", NULL },
+  { "allow-downgrade", 0, 0, G_OPTION_ARG_NONE, &opt_allow_downgrade, "Permit deployment of chronologically older trees", NULL },
   { NULL }
 };
 
@@ -109,6 +111,44 @@ ot_admin_builtin_upgrade (int argc, char **argv, OstreeSysroot *sysroot, GCancel
   else
     {
       gs_unref_object GFile *real_sysroot = g_file_new_for_path ("/");
+
+      if (!opt_allow_downgrade)
+        {
+          const char *old_revision = ostree_deployment_get_csum (merge_deployment);
+          gs_unref_variant GVariant *old_commit = NULL;
+          gs_unref_variant GVariant *new_commit = NULL;
+
+          if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT,
+                                         old_revision,
+                                         &old_commit,
+                                         error))
+            goto out;
+          
+          if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT,
+                                         new_revision, &new_commit,
+                                         error))
+            goto out;
+
+          if (ostree_commit_get_timestamp (old_commit) > ostree_commit_get_timestamp (new_commit))
+            {
+              GDateTime *old_ts = g_date_time_new_from_unix_utc (ostree_commit_get_timestamp (old_commit));
+              GDateTime *new_ts = g_date_time_new_from_unix_utc (ostree_commit_get_timestamp (new_commit));
+              gs_free char *old_ts_str = NULL;
+              gs_free char *new_ts_str = NULL;
+
+              g_assert (old_ts);
+              g_assert (new_ts);
+              old_ts_str = g_date_time_format (old_ts, "%c");
+              new_ts_str = g_date_time_format (new_ts, "%c");
+              g_date_time_unref (old_ts);
+              g_date_time_unref (new_ts);
+
+              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Upgrade target revision '%s' with timestamp '%s' is chronologically older than current revision '%s' with timestamp '%s'; use --allow-downgrade to permit",
+                           new_revision, new_ts_str, old_revision, old_ts_str);
+              goto out;
+            }
+        }
       
       /* Here we perform cleanup of any leftover data from previous
        * partial failures.  This avoids having to call gs_shutil_rm_rf()
index da85c1c88c3ecbc28b939b31c4b07c64c6ad3934..7d726a5cea33c98fb47c8ea487d92d185ccf7ab7 100644 (file)
@@ -223,6 +223,8 @@ EOF
 
     ostree --repo=${test_tmpdir}/testos-repo commit -b testos/buildmaster/x86_64-runtime -s "Build"
     
+    # Ensure these commits have distinct second timestamps
+    sleep 2
     echo "a new executable" > usr/bin/sh
     ostree --repo=${test_tmpdir}/testos-repo commit -b testos/buildmaster/x86_64-runtime -s "Build"
 
@@ -248,6 +250,15 @@ EOF
            setup_os_boot_uboot
             ;;
     esac
+    
+    cd ${test_tmpdir}
+    mkdir ${test_tmpdir}/httpd
+    cd httpd
+    ln -s ${test_tmpdir} ostree
+    ostree trivial-httpd --daemonize -p ${test_tmpdir}/httpd-port $args
+    port=$(cat ${test_tmpdir}/httpd-port)
+    echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
+    cd ${oldpwd} 
 }
 
 os_repository_new_commit ()
diff --git a/tests/test-admin-upgrade-not-backwards.sh b/tests/test-admin-upgrade-not-backwards.sh
new file mode 100644 (file)
index 0000000..6ea988b
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 Colin Walters <walters@verbum.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+set -e
+
+. $(dirname $0)/libtest.sh
+
+echo "1..1"
+
+setup_os_repository "archive-z2" "syslinux"
+
+echo "ok setup"
+
+echo "1..2"
+
+cd ${test_tmpdir}
+ostree --repo=sysroot/ostree/repo remote add --set=gpg-verify=false testos $(cat httpd-address)/ostree/testos-repo
+ostree --repo=sysroot/ostree/repo pull testos testos/buildmaster/x86_64-runtime
+rev=$(ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime)
+export rev
+echo "rev=${rev}"
+# This initial deployment gets kicked off with some kernel arguments 
+ostree admin --sysroot=sysroot deploy --karg=root=LABEL=MOO --karg=quiet --os=testos testos:testos/buildmaster/x86_64-runtime
+assert_has_dir sysroot/boot/ostree/testos-${bootcsum}
+
+# This should be a no-op
+ostree admin --sysroot=sysroot upgrade --os=testos
+
+# Now reset to an older revision
+ostree --repo=${test_tmpdir}/testos-repo reset testos/buildmaster/x86_64-runtime{,^}
+
+if ostree admin --sysroot=sysroot upgrade --os=testos 2>upgrade-err.txt; then
+    assert_not_reached 'upgrade unexpectedly succeeded'
+fi
+assert_file_has_content upgrade-err.txt 'chronologically older'
+
+echo 'ok upgrade will not go backwards'
+
+ostree admin --sysroot=sysroot upgrade --os=testos --allow-downgrade
+
+echo 'ok upgrade backwards'